/*
Copyright (c) 2008  Franklin Schmidt <fschmidt@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

package fschmidt.util.java;

import sun.awt.image.BufferedImageGraphicsConfig;

import javax.imageio.ImageIO;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;


public final class ImageUtils {
	private ImageUtils() {}  // never

	public static ImageDetails saveImage(String imagePath, BufferedImage image, String format)
		throws IllegalArgumentException, IOException
	{
		FileOutputStream fos = new FileOutputStream(imagePath);
		try {
			ImageIO.write(image, format, fos);
		} finally {
			image.flush();
			fos.close();
		}
		return new ImageDetails(image.getHeight(), image.getWidth());
	}

	public static BufferedImage loadImage(String imagePath)
		throws IOException, IllegalArgumentException
	{
		FileInputStream fis = new FileInputStream(imagePath);
		try {
			return createCompatibleImage(ImageIO.read(fis));
		} finally {
			fis.close();
		}
	}

	public static BufferedImage getImage(InputStream in)
		throws IOException, IllegalArgumentException
	{
		BufferedImage img = ImageIO.read(in);
		return img == null? null : createCompatibleImage(img);
	}

	public static void deleteImage(String imagePath) {
		File imageFile = new File(imagePath);
		if (imageFile.exists())
			imageFile.delete();
	}

	private static BufferedImage fixImage(BufferedImage img) {
		// Clone the image using the RGB type to prevent color problems.
		BufferedImage temp = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
		Graphics2D g2 = temp.createGraphics();
		g2.drawImage(img, 0, 0, null);
		g2.dispose();
		return temp;
	}

	public static ByteArrayOutputStream asOutputStream(BufferedImage img, String format) throws IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ImageIO.write(fixImage(img), format, baos);
		return baos;
	}

	public static InputStream asInputStream(BufferedImage img, String format) throws IOException {
		ByteArrayOutputStream baos = asOutputStream(img, format);
		final byte[] bytes = baos.toByteArray();
		return new ByteArrayInputStream(bytes);
	}

	public static BufferedImage cropImage(BufferedImage image, int x, int y, int width, int height)
		throws IllegalArgumentException, IOException
	{
		if (x < 0) x = 0;
		if (y < 0) y = 0;
		if (width > image.getWidth())
			width = image.getWidth();
		if (height > image.getHeight())
			height = image.getHeight();
		if (x + width > image.getWidth())
			x = image.getWidth() - width;
		if (y + height > image.getHeight())
			y = image.getHeight() - height;
		return image.getSubimage(x, y, width, height);
	}

	public static BufferedImage resizeImage(BufferedImage image, int width, int height) {
		int type = image.getType() == 0? BufferedImage.TYPE_INT_ARGB : image.getType();
		BufferedImage resizedImage = new BufferedImage(width, height, type);
		Graphics2D g = resizedImage.createGraphics();
		g.setComposite(AlphaComposite.Src);
		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.drawImage(image, 0, 0, width, height, null);
		g.dispose();
		return resizedImage;
	}

	public static BufferedImage resizeKeepRatio(BufferedImage img, int width, int height) {
		int newWidth = img.getWidth() < img.getHeight()? (int) Math.ceil(img.getWidth() * height / (float) img.getHeight()) : width;
		int newHeight = img.getWidth() < img.getHeight()? height : (int) Math.ceil(img.getHeight() * width / (float) img.getWidth());
		if (newWidth > width) {
			newWidth = width;
			newHeight = img.getHeight() * width / img.getWidth();
		} else if (newHeight > height) {
			newWidth = img.getWidth() * height / img.getHeight();
			newHeight = height;
		}
		return resizeImage(img, newWidth, newHeight);
	}

	public static BufferedImage blurImage(BufferedImage image) {
		float ninth = 1.0f/9.0f;
		float[] blurKernel = {
        	ninth, ninth, ninth,
        	ninth, ninth, ninth,
        	ninth, ninth, ninth
		};

		Map<RenderingHints.Key, Object> map = new HashMap<RenderingHints.Key, Object>();
		map.put(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		map.put(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
		map.put(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
		RenderingHints hints = new RenderingHints(map);
		BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, blurKernel), ConvolveOp.EDGE_NO_OP, hints);
		image = fixImage(image);
		return op.filter(image, null);
	}

	private static BufferedImage createCompatibleImage(BufferedImage image) {
		GraphicsConfiguration gc = BufferedImageGraphicsConfig.getConfig(image);
		int w = image.getWidth();
        int h = image.getHeight();
        BufferedImage result = gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT);
		Graphics2D g2 = result.createGraphics();
        g2.drawRenderedImage(image, null);
        g2.dispose();
        return result;
    }

	public static BufferedImage getThumbnail(BufferedImage img, int width, int height) {
		if (img.getWidth() <= width && img.getHeight() <= height)
			return img;
		else {
			int widthLimit = 3 * width;
			int heightLimit = 3 * height;
			if (img.getWidth() > widthLimit || img.getHeight() > heightLimit) {
				img = resizeKeepRatio(img, widthLimit, heightLimit);
				img = blurImage(img);
			}
			return resizeKeepRatio(img, width, height);
		}
	}

	public static final class ImageDetails {
		private int height;
		private int width;

		public ImageDetails(int height, int width) {
			this.height = height;
			this.width = width;
		}

		public int getHeight() { return height; }
		public int getWidth() { return width; }
	}

/*	public static void main(String[] args) {
		// 140 x 100 px
		try {
			testThumbnail("a.png", "a1");
			testThumbnail("b.jpg", "b1");
			testThumbnail("c.png", "c1");
			testThumbnail("d.jpg", "d1");
			testThumbnail("e.jpg", "e1");
			testThumbnail("f.jpg", "f1");
			testThumbnail("g.png", "g1");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private static void testThumbnail(String fileIn, String fileOut) throws IOException {
		String format = "jpeg";
		BufferedImage img = loadImage("c:\\" + fileIn);
		System.out.println(fileIn + "  w:" + img.getWidth() + " h:" + img.getHeight());
		long start = System.currentTimeMillis();
		BufferedImage thumb = getThumbnail(img, 140, 100);

		BufferedImage temp = new BufferedImage(thumb.getWidth(), thumb.getHeight(), BufferedImage.TYPE_INT_RGB);
		Graphics2D g2 = temp.createGraphics();
		g2.drawImage(thumb, 0, 0, null);
		g2.dispose();
		thumb = temp;

		System.out.println("Time = " + (System.currentTimeMillis()-start));
		saveImage("c:\\"+fileOut+'.'+format, thumb, format);
	}*/
}
